/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
#include "DatabaseEnv.h"
#include "Mail.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Log.h"
#include "World.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "Language.h"
#include "DBCStores.h"
#include "Item.h"

void WorldSession::HandleSendMail(WorldPacket & recv_data) {
	uint64 mailbox, unk3, money, COD;
	std::string receiver, subject, body;
	uint32 unk1, unk2;
	uint8 unk4;
	recv_data >> mailbox;
	recv_data >> receiver;

	recv_data >> subject;

	recv_data >> body;

	recv_data >> unk1; // stationery?
	recv_data >> unk2; // 0x00000000

	uint8 items_count;
	recv_data >> items_count; // attached items count

	if (items_count > MAX_MAIL_ITEMS) // client limit
	{
		GetPlayer()->SendMailResult(0, MAIL_SEND,
				MAIL_ERR_TOO_MANY_ATTACHMENTS);
		recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam
		return;
	}

	uint64 itemGUIDs[MAX_MAIL_ITEMS];

	for (uint8 i = 0; i < items_count; ++i) {
		recv_data.read_skip<uint8>(); // item slot in mail, not used
		recv_data >> itemGUIDs[i];
	}

	recv_data >> money >> COD; // money and cod
	recv_data >> unk3; // const 0
	recv_data >> unk4; // const 0

	// packet read complete, now do check

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	if (receiver.empty())
		return;

	Player* pl = _player;

	if (pl->getLevel() < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ)) {
		SendNotification(GetArkCoreString(LANG_MAIL_SENDER_REQ),
				sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
		return;
	}

	uint64 rc = 0;
	if (normalizePlayerName(receiver))
		rc = sObjectMgr->GetPlayerGUIDByName(receiver);

	if (!rc) {
		sLog->outDetail(
				"Player %u is sending mail to %s (GUID: not existed!) with subject %s and body %s includes %u items,  "UI64FMTD" copper and  "UI64FMTD" COD copper with unk1 = %u, unk2 = %u",
				pl->GetGUIDLow(), receiver.c_str(), subject.c_str(),
				body.c_str(), items_count, money, COD, unk1, unk2);
		pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_NOT_FOUND);
		return;
	}

	sLog->outDetail(
			"Player %u is sending mail to %s (GUID: %u) with subject %s and body %s includes %u items,  "UI64FMTD" copper and  "UI64FMTD" COD copper with unk1 = %u, unk2 = %u",
			pl->GetGUIDLow(), receiver.c_str(), GUID_LOPART(rc),
			subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2);

	if (pl->GetGUID() == rc) {
		pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF);
		return;
	}

	uint32 cost = items_count ? 30 * items_count : 30; // price hardcoded in client

	uint64 reqmoney = cost + money;

	if (!pl->HasEnoughMoney(uint32(reqmoney))) {
		pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_ENOUGH_MONEY);
		return;
	}

	Player *receive = sObjectMgr->GetPlayer(rc);

	uint32 rc_team = 0;
	uint8 mails_count = 0; //do not allow to send to one player more than 100 mails
	uint8 receiveLevel = 0;

	if (receive) {
		rc_team = receive->GetTeam();
		mails_count = receive->GetMailSize();
		receiveLevel = receive->getLevel();
	} else {
		rc_team = sObjectMgr->GetPlayerTeamByGUID(rc);
		if (QueryResult result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc))) {
			Field *fields = result->Fetch();
			mails_count = fields[0].GetUInt32();
		}
		if (QueryResult result = CharacterDatabase.PQuery("SELECT level FROM characters WHERE guid = '%u'", GUID_LOPART(rc))) {
			Field *fields = result->Fetch();
			receiveLevel = fields[0].GetUInt8();
		}
	}
	//do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
	if (mails_count > 100) {
		pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_CAP_REACHED);
		return;
	}
	// test the receiver's Faction... or all items are account bound
	bool accountBound = items_count ? true : false;
	for (uint8 i = 0; i < items_count; ++i) {
		Item* item = pl->GetItemByGuid(itemGUIDs[i]);
		if (item) {
			ItemPrototype const* itemProto = item->GetProto();
			if (!itemProto
					|| !(itemProto->Flags & ITEM_PROTO_FLAG_BIND_TO_ACCOUNT)) {
				accountBound = false;
				break;
			}
		}
	}

	if (!accountBound
			&& !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL)
			&& pl->GetTeam() != rc_team && GetSecurity() == SEC_PLAYER) {
		pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_YOUR_TEAM);
		return;
	}

	if (receiveLevel < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ)) {
		SendNotification(GetArkCoreString(LANG_MAIL_RECEIVER_REQ),
				sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
		return;
	}

	uint32 rc_account =
			receive ?
					receive->GetSession()->GetAccountId() :
					sObjectMgr->GetPlayerAccountIdByGUID(rc);

	Item* items[MAX_MAIL_ITEMS];

	for (uint8 i = 0; i < items_count; ++i) {
		if (!itemGUIDs[i]) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
			return;
		}

		Item* item = pl->GetItemByGuid(itemGUIDs[i]);

		// prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
		if (!item) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
			return;
		}

		if (!item->CanBeTraded(true)) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR,
					EQUIP_ERR_MAIL_BOUND_ITEM);
			return;
		}

		if (item->IsBoundAccountWide() && item->IsSoulBound()
				&& pl->GetSession()->GetAccountId() != rc_account) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR,
					EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS);
			return;
		}

		if (item->GetProto()->Flags & ITEM_PROTO_FLAG_CONJURED
				|| item->GetUInt32Value(ITEM_FIELD_DURATION)) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR,
					EQUIP_ERR_MAIL_BOUND_ITEM);
			return;
		}

		if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_WRAPPED)) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
			return;
		}

		if (item->IsBag() && !((Bag*) item)->IsEmpty()) {
			pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR,
					EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS);
			return;
		}

		items[i] = item;
	}

	pl->SendMailResult(0, MAIL_SEND, MAIL_OK);

	pl->ModifyMoney(-int32(reqmoney));
	pl->GetAchievementMgr().UpdateAchievementCriteria(
			ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL, cost);

	bool needItemDelay = false;

	MailDraft draft(subject, body);

	SQLTransaction trans = CharacterDatabase.BeginTransaction();

	if (items_count > 0 || money > 0) {
		if (items_count > 0) {
			for (uint8 i = 0; i < items_count; ++i) {
				Item* item = items[i];
				if (GetSecurity() > SEC_PLAYER
						&& sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE)) {
					sLog->outCommand(
							GetAccountId(),
							"GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)",
							GetPlayerName(), GetAccountId(),
							item->GetProto()->Name1, item->GetEntry(),
							item->GetCount(), receiver.c_str(), rc_account);
				}

				item->SetNotRefundable(GetPlayer()); // makes the item no longer refundable
				pl->MoveItemFromInventory(items[i]->GetBagSlot(),
						item->GetSlot(), true);

				item->DeleteFromInventoryDB(trans); // deletes item from character's inventory
				item->SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
				// owner in data will set at mail receive and item extracting
				PreparedStatement* stmt =
						CharacterDatabase.GetPreparedStatement(
								CHAR_SET_ITEM_OWNER);
				stmt->setUInt32(0, GUID_LOPART(rc));
				stmt->setUInt32(1, item->GetGUIDLow());
				trans->Append(stmt);

				draft.AddItem(item);
			}

			// if item send to character at another account, then apply item delivery delay
			needItemDelay = pl->GetSession()->GetAccountId() != rc_account;
		}

		if (money > 0 && GetSecurity() > SEC_PLAYER
				&& sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE)) {
			sLog->outCommand(
					GetAccountId(),
					"GM %s (Account: %u) mail money:  "UI64FMTD" to player: %s (Account: %u)",
					GetPlayerName(), GetAccountId(), money, receiver.c_str(),
					rc_account);
		}
	}

	// If theres is an item, there is a one hour delivery delay if sent to another account's character.
	uint32 deliver_delay =
			needItemDelay ?
					sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;

	// will delete item or place to receiver mail list
	draft.AddMoney(money).AddCOD(COD).SendMailTo(trans,
			MailReceiver(receive, GUID_LOPART(rc)), MailSender(pl),
			body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY,
			deliver_delay);

	pl->SaveInventoryAndGoldToDB(trans);
	CharacterDatabase.CommitTransaction(trans);
}

//called when mail is read
void WorldSession::HandleMailMarkAsRead(WorldPacket & recv_data)
{
    uint64 mailbox;
    uint32 mailId;

    recv_data >> mailbox;
    recv_data >> mailId;

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;
    Mail* m = player->GetMail(mailId);
    if (m)
    {
        if (player->unReadMails)
            --player->unReadMails;
        m->checked = m->checked | MAIL_CHECK_MASK_READ;
        player->m_mailsUpdated = true;
        m->state = MAIL_STATE_CHANGED;
    }
}

//called when client deletes mail
void WorldSession::HandleMailDelete(WorldPacket & recv_data) {
	uint64 mailbox;
	uint32 mailId;
	recv_data >> mailbox;
	recv_data >> mailId;
	recv_data.read_skip<uint32>(); // mailTemplateId

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	Mail *m = _player->GetMail(mailId);
	Player* pl = _player;
	pl->m_mailsUpdated = true;
	if (m) {
		// delete shouldn't show up for COD mails
		if (m->COD) {
			pl->SendMailResult(mailId, MAIL_DELETED, MAIL_ERR_INTERNAL_ERROR);
			return;
		}

		m->state = MAIL_STATE_DELETED;
	}
	pl->SendMailResult(mailId, MAIL_DELETED, MAIL_OK);
}

void WorldSession::HandleMailReturnToSender(WorldPacket & recv_data) {
	uint64 mailbox;
	uint32 mailId;
	recv_data >> mailbox;
	recv_data >> mailId;
	recv_data.read_skip<uint64>(); // original sender GUID for return to, not used

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	Player *pl = _player;
	Mail *m = pl->GetMail(mailId);
	if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) {
		pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER,
				MAIL_ERR_INTERNAL_ERROR);
		return;
	}
	//we can return mail now
	//so firstly delete the old one
	SQLTransaction trans = CharacterDatabase.BeginTransaction();
	trans->PAppend("DELETE FROM mail WHERE id = '%u'", mailId); // needed?
	trans->PAppend("DELETE FROM mail_items WHERE mail_id = '%u'", mailId);
	CharacterDatabase.CommitTransaction(trans);
	pl->RemoveMail(mailId);

	// only return mail if the player exists (and delete if not existing)
	if (m->messageType == MAIL_NORMAL && m->sender) {
		MailDraft draft(m->subject, m->body);
		if (m->mailTemplateId)
			draft = MailDraft(m->mailTemplateId, false); // items already included

		if (m->HasItems()) {
			for (MailItemInfoVec::iterator itr2 = m->items.begin();
					itr2 != m->items.end(); ++itr2) {
				Item *item = pl->GetMItem(itr2->item_guid);
				if (item)
					draft.AddItem(item);
				else {
					//WTF?
				}

				pl->RemoveMItem(itr2->item_guid);
			}
		}
		draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver,
				m->sender, trans);
	}
	CharacterDatabase.CommitTransaction(trans);

	delete m; //we can deallocate old mail
	pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK);
}

//called when player takes item attached in mail
void WorldSession::HandleMailTakeItem(WorldPacket & recv_data) {
	uint64 mailbox;
	uint32 mailId;
	uint32 itemId;
	recv_data >> mailbox;
	recv_data >> mailId;
	recv_data >> itemId; // item guid low

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	Player* pl = _player;

	Mail* m = pl->GetMail(mailId);
	if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) {
		pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR);
		return;
	}

	// prevent cheating with skip client money check
	if (!pl->HasEnoughMoney(uint32(m->COD))) {
		pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_NOT_ENOUGH_MONEY);
		return;
	}

	Item *it = pl->GetMItem(itemId);

	ItemPosCountVec dest;
	uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, it, false);
	if (msg == EQUIP_ERR_OK) {
		SQLTransaction trans = CharacterDatabase.BeginTransaction();
		m->RemoveItem(itemId);
		m->removedItems.push_back(itemId);

		if (m->COD > 0) //if there is COD, take COD money from player and send them to sender by mail
				{
			uint64 sender_guid = MAKE_NEW_GUID(m->sender, 0, HIGHGUID_PLAYER);
			Player *receive = sObjectMgr->GetPlayer(sender_guid);

			uint32 sender_accId = 0;

			if (GetSecurity() > SEC_PLAYER
					&& sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE)) {
				std::string sender_name;
				if (receive) {
					sender_accId = receive->GetSession()->GetAccountId();
					sender_name = receive->GetName();
				} else {
					// can be calculated early
					sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(
							sender_guid);

					if (!sObjectMgr->GetPlayerNameByGUID(sender_guid,
							sender_name))
						sender_name = sObjectMgr->GetArkCoreStringForDBCLocale(
								LANG_UNKNOWN);
				}
				sLog->outCommand(
						GetAccountId(),
						"GM %s (Account: %u) receive mail item: %s (Entry: %u Count: %u) and send COD money:  "UI64FMTD" to player: %s (Account: %u)",
						GetPlayerName(), GetAccountId(), it->GetProto()->Name1,
						it->GetEntry(), it->GetCount(), m->COD,
						sender_name.c_str(), sender_accId);
			} else if (!receive)
				sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(
						sender_guid);

			// check player existence
			if (receive || sender_accId) {
				MailDraft(m->subject, "").AddMoney(m->COD).SendMailTo(trans,
						MailReceiver(receive, m->sender),
						MailSender(MAIL_NORMAL, m->receiver),
						MAIL_CHECK_MASK_COD_PAYMENT);
			}

			pl->ModifyMoney(-int32(m->COD));
		}
		m->COD = 0;
		m->state = MAIL_STATE_CHANGED;
		pl->m_mailsUpdated = true;
		pl->RemoveMItem(it->GetGUIDLow());

		uint32 count = it->GetCount(); // save counts before store and possible merge with deleting
		pl->MoveItemToInventory(dest, it, true);

		pl->SaveInventoryAndGoldToDB(trans);
		pl->_SaveMail(trans);
		CharacterDatabase.CommitTransaction(trans);

		pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_OK, 0, itemId, count);
	} else
		pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_EQUIP_ERROR, msg);
}

void WorldSession::HandleMailTakeMoney(WorldPacket & recv_data)
{
    uint64 mailbox;
    uint32 mailId;
    recv_data >> mailbox;
    recv_data >> mailId;
    recv_data.read_skip<uint64>();                          //4.0.6a

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;

    Mail* m = player->GetMail(mailId);
    if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
    {
        player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_INTERNAL_ERROR);
        return;
    }

    player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_OK);

    player->ModifyMoney(m->money);
    m->money = 0;
	CharacterDatabase.PExecute("UPDATE mail SET money = '0' WHERE id='%u'", mailId);
	m->checked = m->checked | MAIL_CHECK_MASK_READ;
    player->m_mailsUpdated = true;
	m->state = MAIL_STATE_CHANGED;

    // save money and mail to prevent cheating
    SQLTransaction trans = CharacterDatabase.BeginTransaction();
    player->SaveGoldToDB(trans);
    player->_SaveMail(trans);
    CharacterDatabase.CommitTransaction(trans);
}

//called when player lists his received mails
void WorldSession::HandleGetMailList(WorldPacket & recv_data) {
	uint64 mailbox;
	recv_data >> mailbox;

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	Player* pl = _player;

	//load players mails, and mailed items
	if (!pl->m_mailsLoaded)
		pl->_LoadMail();

	// client can't work with packets > max int16 value
	const uint32 maxPacketSize = 32767;

	uint32 mailsCount = 0; // real send to client mails amount
	uint32 realCount = 0; // real mails amount

	WorldPacket data(SMSG_MAIL_LIST_RESULT, (200));
	data << uint32(0); // real mail's count
	data << uint8(0); // mail's count
	time_t cur_time = time(NULL);

	for (PlayerMails::iterator itr = pl->GetMailBegin();
			itr != pl->GetMailEnd(); ++itr) {
		// packet send mail count as uint8, prevent overflow
		if (mailsCount >= 254) {
			realCount += 1;
			continue;
		}

		// skip deleted or not delivered (deliver delay not expired) mails
		if ((*itr)->state == MAIL_STATE_DELETED
				|| cur_time < (*itr)->deliver_time)
			continue;

		uint8 item_count = (*itr)->items.size(); // max count is MAX_MAIL_ITEMS (12)

		size_t next_mail_size = 2 + 4 + 1
				+ ((*itr)->messageType == MAIL_NORMAL ? 8 : 4) + 4 * 8
				+ ((*itr)->subject.size() + 1) + ((*itr)->body.size() + 1) + 1
				+ item_count
						* (1 + 4 + 4 + 7 * 3 * 4 + 4 + 4 + 4 + 4 + 4 + 4 + 1);

		if (data.wpos() + next_mail_size > maxPacketSize) {
			realCount += 1;
			continue;
		}

		data << uint16(next_mail_size); // Message size
		data << uint32((*itr)->messageID); // Message ID
		data << uint8((*itr)->messageType); // Message Type

		switch ((*itr)->messageType) {
		case MAIL_NORMAL: // sender guid
			data << uint64(MAKE_NEW_GUID((*itr)->sender, 0, HIGHGUID_PLAYER));
			break;
		case MAIL_CREATURE:
		case MAIL_GAMEOBJECT:
		case MAIL_AUCTION:
			data << uint32((*itr)->sender); // creature/gameobject entry, auction id
			break;
		case MAIL_ITEM: // item entry (?) sender = "Unknown", NYI
			data << uint32(0); // item entry
			break;
		}

		data << uint64((*itr)->COD); // COD
		data << uint32(0); // probably changed in 3.3.3
		data << uint32((*itr)->stationery); // stationery (Stationery.dbc)
		data << uint64((*itr)->money); // Gold
		data << uint32((*itr)->checked); // flags
		data << float(((*itr)->expire_time - time(NULL)) / DAY); // Time
		data << uint32((*itr)->mailTemplateId); // mail template (MailTemplate.dbc)
		data << (*itr)->subject; // Subject string - once 00, when mail type = 3, max 256
		data << (*itr)->body; // message? max 8000
		data << uint8(item_count); // client limit is 0x10

		for (uint8 i = 0; i < item_count; ++i) {
			Item *item = pl->GetMItem((*itr)->items[i].item_guid);
			// item index (0-6?)
			data << uint8(i);
			// item guid low?
			data << uint32((item ? item->GetGUIDLow() : 0));
			// entry
			data << uint32((item ? item->GetEntry() : 0));
			for (uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; ++j) {
				data
						<< uint32(
								(item ? item->GetEnchantmentId(
												(EnchantmentSlot) j) :
										0));
				data
						<< uint32(
								(item ? item->GetEnchantmentDuration(
												(EnchantmentSlot) j) :
										0));
				data
						<< uint32(
								(item ? item->GetEnchantmentCharges(
												(EnchantmentSlot) j) :
										0));
			}

			for (uint8 j = 0; j < 2; ++j) {
				data << uint32(0);
				data << uint32(0);
				data << uint32(0);
			}

			// can be negative
			data << int32((item ? item->GetItemRandomPropertyId() : 0));
			// unk
			data << uint32((item ? item->GetItemSuffixFactor() : 0));
			// stack count
			data << uint32((item ? item->GetCount() : 0));
			// charges
			data << uint32((item ? item->GetSpellCharges() : 0));
			// durability
			data
					<< uint32(
							(item ? item->GetUInt32Value(
											ITEM_FIELD_MAXDURABILITY) :
									0));
			// durability
			data
					<< uint32(
							(item ? item->GetUInt32Value(
											ITEM_FIELD_DURABILITY) :
									0));
			// unknown wotlk
			data << uint8(0);
		}

		realCount += 1;
		mailsCount += 1;
	}

	data.put<uint32>(0, realCount); // this will display warning about undelivered mail to player if realCount > mailsCount
	data.put<uint8>(4, mailsCount); // set real send mails to client
	SendPacket(&data);

	// recalculate m_nextMailDelivereTime and unReadMails
	_player->UpdateNextMailTimeAndUnreads();
}

//used when player copies mail body to his inventory
void WorldSession::HandleMailCreateTextItem(WorldPacket & recv_data) {
	uint64 mailbox;
	uint32 mailId;

	recv_data >> mailbox;
	recv_data >> mailId;

	if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox,
			GAMEOBJECT_TYPE_MAILBOX))
		return;

	Player *pl = _player;

	Mail* m = pl->GetMail(mailId);
	if (!m || (m->body.empty() && !m->mailTemplateId)
			|| m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) {
		pl->SendMailResult(mailId, MAIL_MADE_PERMANENT,
				MAIL_ERR_INTERNAL_ERROR);
		return;
	}

	Item *bodyItem = new Item; // This is not bag and then can be used new Item.
	if (!bodyItem->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, pl))
	{
		delete bodyItem;
		return;
	}

	// in mail template case we need create new item text
	if (m->mailTemplateId) {
		MailTemplateEntry const* mailTemplateEntry =
				sMailTemplateStore.LookupEntry(m->mailTemplateId);
		if (!mailTemplateEntry) {
			pl->SendMailResult(mailId, MAIL_MADE_PERMANENT,
					MAIL_ERR_INTERNAL_ERROR);
			return;
		}

		bodyItem->SetText(mailTemplateEntry->content);
	} else
		bodyItem->SetText(m->body);

	bodyItem->SetUInt32Value(ITEM_FIELD_CREATOR, m->sender);
	bodyItem->SetFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_MAIL_TEXT_MASK);

	sLog->outDetail("HandleMailCreateTextItem mailid=%u", mailId);

	ItemPosCountVec dest;
	uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, bodyItem,
			false);
	if (msg == EQUIP_ERR_OK) {
		m->checked = m->checked | MAIL_CHECK_MASK_COPIED;
		m->state = MAIL_STATE_CHANGED;
		pl->m_mailsUpdated = true;

		pl->StoreItem(dest, bodyItem, true);
		pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_OK);
	} else {
		pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_EQUIP_ERROR,
				msg);
		delete bodyItem;
	}
}

//TODO Fix me! ... this void has probably bad condition, but good data are sent
void WorldSession::HandleQueryNextMailTime(WorldPacket & /*recv_data*/) {
	WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8);

	if (!_player->m_mailsLoaded)
		_player->_LoadMail();

	if (_player->unReadMails > 0) {
		data << uint32(0); // float
		data << uint32(0); // count

		uint32 count = 0;
		time_t now = time(NULL);
		for (PlayerMails::iterator itr = _player->GetMailBegin();
				itr != _player->GetMailEnd(); ++itr) {
			Mail *m = (*itr);
			// must be not checked yet
			if (m->checked & MAIL_CHECK_MASK_READ)
				continue;

			// and already delivered
			if (now < m->deliver_time)
				continue;

			if (m->messageType)
				data << uint64(m->sender); // player guid
			else
				data << uint32(m->sender); // creature entry

			switch (m->messageType) {
			case MAIL_AUCTION:
				data << uint32(2);
				data << uint32(2);
				data << uint32(m->stationery);
				break;
			default:
				data << uint32(0);
				data << uint32(0);
				data << uint32(m->stationery);
				break;
			}
			data << uint32(0xC6000000); // float unk, time or something

			++count;
			if (count == 2) // do not display more than 2 mails
				break;
		}
		data.put<uint32>(4, count);
	} else {
		data << uint32(0xC7A8C000);
		data << uint32(0x00000000);
	}
	SendPacket(&data);
}
